﻿using System.Collections.Concurrent;
using System.Net.WebSockets;
using System.Text;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;

namespace Md.Im.Service;

public static class MdImServerExtenssions
{
	static bool isUseWebSockets = false;

	public static IApplicationBuilder UseMdImServer(this IApplicationBuilder app, ImServerOptions options)
	{
		app.Map(options.PathMatch, appcur =>
		{
			var imService = new ImService(options);

			if (isUseWebSockets == false)
			{
				isUseWebSockets = true;
				appcur.UseWebSockets();
			}

			appcur.Use((ctx, next) => imService.Acceptor(ctx, next));
		});

		return app;
	}
}

/// <summary>
/// im 核心实现配置
/// </summary>
public class ImServerOptions : ImClientOptions
{
	/// <summary>
	/// 设置服务名称，它应该是 servers 内的一个
	/// </summary>
	public string Server { get; set; }
}

public class ImService:ImClient
{
	protected string _server { get; set; }

	public ImService(ImServerOptions options):base(options)
	{
		_server = options.Server;
		_redis.Subscribe($"{_redisPrefix}Server{_server}", RedisSubScribleMessage);
	}

	const int BufferSize = 4096;
	ConcurrentDictionary<Guid, ConcurrentDictionary<Guid, ImServerClient>> _clients = new ConcurrentDictionary<Guid, ConcurrentDictionary<Guid, ImServerClient>>();

	class ImServerClient
	{
		public WebSocket socket;
		public Guid clientId;

		public ImServerClient(WebSocket _socket, Guid _clientId)
		{
			this.socket = _socket;
			this.clientId = _clientId;
		}
	}

    internal async Task Acceptor(HttpContext context, Func<Task> next)
	{
		if (!context.WebSockets.IsWebSocketRequest) return;

		string token = context.Request.Query["token"];
		if (string.IsNullOrEmpty(token)) return;

		var token_value = _redis.Get($"{_redisPrefix}Token{token}");
		if (string.IsNullOrEmpty(token_value)) throw new Exception("授权错误: 用户通过 ImHelper.PrevConnectServer 获取包含 token 的连接");

		var data = JsonConvert.DeserializeObject<(Guid clientId, string clientMetaData)>(token_value);

		Console.WriteLine($"{data}---1");

		var socket = await context.WebSockets.AcceptWebSocketAsync();

		var cli = new ImServerClient(socket, data.clientId);

		var newId = Guid.NewGuid();

		var wsList = _clients.GetOrAdd(data.clientId, cliId => new ConcurrentDictionary<Guid, ImServerClient>());

        wsList.TryAdd(newId, cli);

		Console.WriteLine($"{JsonConvert.SerializeObject(wsList)}---2");

		using (var pipe = _redis.StartPipe())
		{
			pipe.HIncrBy($"{_redisPrefix}OnLine", data.clientId.ToString(), 1);
			pipe.Publish($"evt_{_redisPrefix}OnLine", token_value);
			pipe.EndPipe();
		}

		var buffer = new byte[BufferSize];

		var seg = new ArraySegment<byte>(buffer);

		try
		{
			while (socket.State == WebSocketState.Open && _clients.ContainsKey(data.clientId))
			{
				var incoming = await socket.ReceiveAsync(seg, CancellationToken.None);
				var outgoing = new ArraySegment<byte>(buffer, 0, incoming.Count);
			}
			socket.Abort();
		}
		catch
		{
		}

		wsList.TryRemove(newId, out var oldCli);

		if (wsList.Any() == false) _clients.TryRemove(data.clientId, out var oldWsList);

		_redis.Eval($"if redis.call('HINCRBY',KEYS[1], '{data.clientId}', '-1') <=0 then redis.call('HDEL', KEYS[1], '{data.clientId}') end return 1", new[] { $"{_redisPrefix}OnLine" });

		LeaveChan(data.clientId, GetChanListByClientId(data.clientId));

		_redis.Publish($"evt_${_redisPrefix}OffLine", token_value);
	}


	void RedisSubScribleMessage(string chan, object msg)
	{
		Console.WriteLine($"{chan}-{msg}---");
		try
		{
			var msgtxt = msg as string;

			if (msgtxt.StartsWith("__MdIM__(ForceOffLine)"))
			{
				if (Guid.TryParse(msgtxt.Substring(24), out var clientId) && _clients.TryRemove(clientId, out var oldClients))
				{
					foreach (var oldCli in oldClients)
					{
						try { oldCli.Value.socket.CloseAsync(WebSocketCloseStatus.EndpointUnavailable, "disconnect", CancellationToken.None).GetAwaiter().GetResult(); } catch { }
						try { oldCli.Value.socket.Abort(); } catch { }
						try { } catch { oldCli.Value.socket.Dispose(); }
					}
				}
				return;
			}

			var data = JsonConvert.DeserializeObject<(Guid senderClientId, List<Guid> receiveClientId, string content, bool receipt)>(msgtxt);

			var outgoing = new ArraySegment<byte>(Encoding.UTF8.GetBytes(data.content));

			foreach (var clientId in data.receiveClientId)
			{
				if (_clients.TryGetValue(clientId, out var wsList) == false)
				{
					if (data.senderClientId != Guid.Empty && clientId != data.senderClientId && data.receipt)
					{
						SendMessage(clientId, new[] { data.senderClientId }, new { data.content, receipt = "用户不在线" });
					}
					continue;
				}



				ImServerClient[] socketArray = wsList.Values.ToArray();

				if (clientId == data.senderClientId && socketArray.Length <= 1) continue;

				foreach (var sh in socketArray)
				{
					sh.socket.SendAsync(outgoing, WebSocketMessageType.Text, true, CancellationToken.None).ContinueWith(async (t, state) =>
					{
						if (t.Exception != null)
						{
							var ws = state as WebSocket;
							try { await ws.CloseAsync(WebSocketCloseStatus.EndpointUnavailable, "disconnect", CancellationToken.None); } catch { }
							try { ws.Abort(); } catch { }
							try { ws.Dispose(); } catch { }
						}
					}, sh.socket);
				}

				if(data.senderClientId!=Guid.Empty && clientId!=data.senderClientId && data.receipt)
				{
					SendMessage(clientId, new[] { data.senderClientId }, new { data.content, receipt = "发送成功" });
				}
			}

		}
		catch (Exception ex)
		{
			Console.WriteLine($"MdIM.ImServer 订阅方法出错了：{ex.Message}\r\n{ex.StackTrace}");
		}
	}
}

